package com.easylinkin.linkappapi.meterbilling.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.bases.redis.util.RedisUtil;
import com.easylinkin.linkappapi.common.utils.DateUtil;
import com.easylinkin.linkappapi.meterbilling.entity.*;
import com.easylinkin.linkappapi.meterbilling.entity.ext.GlobalRoomInfo;
import com.easylinkin.linkappapi.meterbilling.mapper.*;
import com.easylinkin.linkappapi.meterbilling.service.*;
import com.easylinkin.linkappapi.meterbilling.util.MeterServiceControl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * class info :房间支出消费任务
 *
 * @author liuqihang
 * @date 2021/7/15 14:35
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class RoomExpensesServiceImpl extends ServiceImpl<RoomExpensesMapper, RoomExpensesBill> implements RoomExpensesService, Job {

    @Resource
    private ExpensesTimerTaskMapper expensesTimerTaskMapper;

    @Resource
    private BaseMeterBillingMapper baseMeterBillingMapper;

    @Resource
    private BillingStandardService billingStandardService;

    @Resource
    private EnergyConsumptionService energyConsumptionService;

    @Resource
    private RoomManagerService roomManagerService;

    @Resource
    private RoomInfoMapper roomInfoMapper;

    @Resource
    private MeterDeviceConfigMapper meterDeviceConfigMapper;

    @Resource
    private MeterRemedyMapper meterRemedyMapper;

    @Resource@Lazy
    private MeterRemedyService meterRemedyService;

    @Resource
    private RedissonClient redisson;

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private MeterServiceControl meterServiceControl;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("start execute room expenses task ................ start");
        meterTimerTask();
        log.info("start execute room expenses task ................ end");
    }

    @Override
    public void meterTimerTask() {
        String lockKey = DateUtil.format(new Date(), DateUtil.YYYYMMDDHH_STRING);
        RLock redissonLock = redisson.getLock("room_expenses_" + lockKey);
        if (redissonLock.tryLock()) {
            try {
                String usedKey = lockKey + "_room_timer";
                if (redisUtil.hasKey(usedKey)) {
                    log.info("redis 已存在key值：'" + usedKey + "', 任务执行结束");
                    return;
                }
                redisUtil.set(usedKey, true, 60);
                //开始执行扣费业务逻辑
                taskLogic();
            } catch (Exception ex) {
                log.error("meterTimerTask 房间消费扣款任务异常 error info=:", ex);
            } finally {
                //如果不加isHeldByCurrentThread判断条件的话，偶发异常：attempt to unlock lock, not locked by current thread by node id
                //lock.isLocked()：判断要解锁的key是否已被锁定。
                //lock.isHeldByCurrentThread()：判断要解锁的key是否被当前线程持有。
                if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
                    redissonLock.unlock();
                }
            }
        }else {
            log.info("获取锁'room_expenses_" + lockKey + "'失败");
        }
    }

    private void taskLogic(){
        //获取补录的设备单独计算
        List<MeterRemedy> meterRemedyList = getRemedyDeviceList(null, true);
        QueryWrapper qw = new QueryWrapper();
        qw.eq("delete_status","0");

        //存在补录设备需要排除，进入单个设备请求扣费逻辑
        if(!ObjectUtils.isEmpty(meterRemedyList)){
            log.info("补录的设备数量 size =" + meterRemedyList.size() + ", 进入单个设备请求");
            List<String> deviceCodes = meterRemedyList.stream().map(i->i.getDeviceCode()).collect(Collectors.toList());
            singleDeviceHandler(deviceCodes);
            qw.notIn("device_code", deviceCodes);
        }
        List<MeterDeviceConfig> list = meterDeviceConfigMapper.selectList(qw);
        if(list == null || list.isEmpty()){
            log.info("无仪表设备记录（正常设备）");
            return ;
        }

        Date[] currentSettlementTimePeriod = getCurrentSettlementTimePeriod();

        Map<String, List<RoomExpensesBill>> newestRoomExpensesBillRecordByDeviceCode = getNewestRoomExpensesBillRecordByDeviceCode(list);
        List<EnergyConsumption> energyConsumptionDateList = new ArrayList<>();

        //获取ES数据
        if(newestRoomExpensesBillRecordByDeviceCode == null){
            //系统中无扣费记录，所有设备按新设备处理，计算能耗周期从设备绑定日期开始
            for (MeterDeviceConfig meterDeviceConfig:
                    list) {
                List<EnergyConsumption> childEnergyConsumptionDateList = getNewDeviceEnergyConsumptionCalculation(meterDeviceConfig);
                if(ObjectUtils.isNotEmpty(childEnergyConsumptionDateList)){
                    EnergyConsumption energyConsumption = childEnergyConsumptionDateList.get(0);
                    if(energyConsumption.getAmount() != null || (energyConsumption.getAmount().compareTo(new BigDecimal(0)) > 0)){
                        energyConsumptionDateList.addAll(childEnergyConsumptionDateList);
                    }
                }
            }
        }else {
            //筛选设备
            handlerMeterDeviceConfigIsNewBindField(list, newestRoomExpensesBillRecordByDeviceCode);
            //扣费时间比绑定时间还早的设备
            List<MeterDeviceConfig> newBindDeviceList = list.stream().filter(e -> e.isNewBind()).collect(Collectors.toList());
            //扣费时间比绑定时间晚的设备(正常设备)
            List<MeterDeviceConfig> oldBindDeviceList = list.stream().filter(e -> !(e.isNewBind())).collect(Collectors.toList());

            if(ObjectUtils.isNotEmpty(newBindDeviceList)){
                for (MeterDeviceConfig meterDeviceConfig:
                        newBindDeviceList) {
                    List<EnergyConsumption> childEnergyConsumptionDateList = getNewDeviceEnergyConsumptionCalculation(meterDeviceConfig);
                    if(ObjectUtils.isNotEmpty(childEnergyConsumptionDateList)){
                        EnergyConsumption energyConsumption = childEnergyConsumptionDateList.get(0);
                        if(energyConsumption.getAmount() != null || (energyConsumption.getAmount().compareTo(new BigDecimal(0)) > 0)){
                            energyConsumptionDateList.addAll(childEnergyConsumptionDateList);
                        }
                    }
                }
            }
            if(ObjectUtils.isNotEmpty(oldBindDeviceList)){
                List<EnergyConsumption> energyConsumptions = calculationLastAndCurrntEnergyConsumptionDateList(currentSettlementTimePeriod, oldBindDeviceList, newestRoomExpensesBillRecordByDeviceCode);
                if(ObjectUtils.isNotEmpty(energyConsumptions)){
                    energyConsumptionDateList.addAll(energyConsumptions);
                }
            }
        }

        if(energyConsumptionDateList == null || energyConsumptionDateList.isEmpty()){
            log.info("无设备流水记录");
            return ;
        }
        handlerData(energyConsumptionDateList);

        //保存定时任务运行时间段
        ExpensesTimerTask expensesTimerTask = new ExpensesTimerTask();
        saveSettlementTimePeriod(expensesTimerTask, currentSettlementTimePeriod);
    }

    /**
     * 获取单个新设备能耗（从绑定创建时间开始计算）
     * @param meterDeviceConfig
     * @return
     */
    private List<EnergyConsumption> getNewDeviceEnergyConsumptionCalculation(MeterDeviceConfig meterDeviceConfig){
        Date[] settlementTimePeriod = new Date[2];
        settlementTimePeriod[0] = DateUtil.localDateTimeToDate(meterDeviceConfig.getCreateTime());
        settlementTimePeriod[1] = new Date();
        List<MeterDeviceConfig> childList = new ArrayList<>();
        childList.add(meterDeviceConfig);
        List<EnergyConsumption> childEnergyConsumptionDateList = getEnergyConsumptionDateList(settlementTimePeriod, childList);
        return childEnergyConsumptionDateList;
    }

    /**
     * 是否是新绑定的设备
     * @param bindDate  水电表管理设备新增时间
     * @param compareDate   比较时间，例如最新扣费记录时间、最新补录时间
     * @return
     */
    private boolean checkIsNewBindDevice(Date bindDate, Date compareDate){
        boolean newDeviceFlag = false;
        if(compareDate.compareTo(bindDate) < 0){
            newDeviceFlag = true;
        }
        return newDeviceFlag;
    }

    public void singleDeviceHandler(List<String> deviceCodeList){
        for (String deviceCode:
                deviceCodeList) {
            QueryWrapper qw = new QueryWrapper();
            qw.eq("delete_status","0");
            qw.eq("device_code", deviceCode);
            List<MeterDeviceConfig> meterDeviceConfigList = meterDeviceConfigMapper.selectList(qw);
            if(ObjectUtils.isEmpty(meterDeviceConfigList)){  continue; }

            List<MeterRemedy> meterRemedyList = getRemedyDeviceList(deviceCode, false);
            if(ObjectUtils.isEmpty(meterRemedyList)){
                continue;
            }
            MeterRemedy meterRemedy = meterRemedyList.get(0);
            MeterDeviceConfig meterDeviceConfig = meterDeviceConfigList.get(0);
            Date[] settlementDate = getCurrentSettlementTimePeriod();
            boolean newBindDevice = checkIsNewBindDevice(DateUtil.localDateTimeToDate(meterDeviceConfig.getCreateTime()), meterRemedy.getCreateTime());
            if(newBindDevice){
                settlementDate[0] = DateUtil.localDateTimeToDate(meterDeviceConfig.getCreateTime());
            }
            //获取设备数据
            List<EnergyConsumption> energyConsumptionDateList = getEnergyConsumptionDateList(settlementDate, meterDeviceConfigList);

            if(ObjectUtils.isEmpty(energyConsumptionDateList)){
                continue;
            }else {
                EnergyConsumption ec = energyConsumptionDateList.get(0);
                if(ObjectUtils.isEmpty(ec.getEndRead())){
                    continue;
                }
                //被减数为补录的止码
                if(!newBindDevice){
                    ec.setStartRead(meterRemedy.getEnergyAmount());
                    ec.setAmount(ec.getEndRead().subtract(meterRemedy.getEnergyAmount()));
                    ec.setQueryPayTimeStart(meterRemedy.getCreateTime());
                    ec.setQueryPayTimeEnd(new Date());
                }
                calculation(ec, null, 0);

                //修改补录设备为已计算状态
                meterRemedyService.restoreDeviceCalculateStatus(deviceCode, null);
            }
        }
    }

    /**
     * 设备创建时间 和 最新扣费时间 比较，筛选出新绑定的设备(isNewBind == true)
     * @param list
     * @param newestRoomExpensesBillRecordByDeviceCode
     */
    private void handlerMeterDeviceConfigIsNewBindField(List<MeterDeviceConfig> list, Map<String, List<RoomExpensesBill>> newestRoomExpensesBillRecordByDeviceCode){
        Map<String, List<MeterDeviceConfig>> collect = list.stream().collect(Collectors.groupingBy(MeterDeviceConfig::getDeviceCode));
        for (Map.Entry<String, List<MeterDeviceConfig>> entry : collect.entrySet()){
            List<MeterDeviceConfig> value = entry.getValue();
            MeterDeviceConfig meterDeviceConfig = value.get(0);

            if(newestRoomExpensesBillRecordByDeviceCode.containsKey(entry.getKey())){
                List<RoomExpensesBill> roomExpensesBillList = newestRoomExpensesBillRecordByDeviceCode.get(entry.getKey());
                RoomExpensesBill roomExpensesBill = roomExpensesBillList.get(0);
                meterDeviceConfig.setCompareDate(roomExpensesBill.getCreateTime());
            }else {
                meterDeviceConfig.setNewBind(true);
            }
        }
    }

    /**
     * 获取最新时段仪表读数 减去 上次时段仪表读数 得到实际仪表能耗
     * @param currentSettlementTimePeriod
     * @param list
     * @return
     */
    public List<EnergyConsumption> calculationLastAndCurrntEnergyConsumptionDateList(Date[] currentSettlementTimePeriod, List<MeterDeviceConfig> list, Map<String, List<RoomExpensesBill>> newestRoomExpensesBillRecordByDeviceCode){
        Map<String, List<MeterDeviceConfig>> meterDeviceMap = list.stream().collect(Collectors.groupingBy(MeterDeviceConfig::getDeviceCode));
        List<EnergyConsumption> resultEnergyConsumptionDateList = new ArrayList<>();

        List<EnergyConsumption> newEnergyConsumptionDateList = getEnergyConsumptionDateList(currentSettlementTimePeriod, list);
        if(ObjectUtils.isEmpty(newEnergyConsumptionDateList)){
            return null;
        }
        for (EnergyConsumption data:
             newEnergyConsumptionDateList) {
            String deviceCode = data.getDeviceCode();
            if(data.getEndRead() == null){
                continue;
            }
            if(ObjectUtils.isEmpty(newestRoomExpensesBillRecordByDeviceCode.get(deviceCode))){
                //重新获取从0开始的设备流水
                List<MeterDeviceConfig> meterDeviceConfigList = meterDeviceMap.get(deviceCode);
                Date[] childTimePeriod = new Date[2];
                childTimePeriod[0] = null;
                childTimePeriod[1] = currentSettlementTimePeriod[1];
                List<EnergyConsumption> childEnergyConsumptionDateList = getEnergyConsumptionDateList(childTimePeriod, meterDeviceConfigList);
                if(ObjectUtils.isEmpty(childEnergyConsumptionDateList)){
                    continue;
                }
                EnergyConsumption energyConsumption = childEnergyConsumptionDateList.get(0);
                if(energyConsumption.getEndRead() == null){
                    continue;
                }
                data.setStartRead(energyConsumption.getStartRead());
                data.setEndRead(energyConsumption.getEndRead());
                data.setAmount(energyConsumption.getAmount());
            }else {
                List<RoomExpensesBill> roomExpensesBillList = newestRoomExpensesBillRecordByDeviceCode.get(deviceCode);
                RoomExpensesBill roomExpensesBill = roomExpensesBillList.get(0);
                if(roomExpensesBill.getEndReadAmount() == null){
                    continue;
                }
                data.setStartRead(roomExpensesBill.getEndReadAmount());
                data.setAmount(data.getEndRead().subtract(roomExpensesBill.getEndReadAmount()));
            }
            resultEnergyConsumptionDateList.add(data);
        }
        return resultEnergyConsumptionDateList;
    }

    /**
     * 获取最新设备消费止码数据
     * @param list
     * @return
     */
    @Override
    public Map<String, List<RoomExpensesBill>> getNewestRoomExpensesBillRecordByDeviceCode(List<MeterDeviceConfig> list){
        try {
            List<String> deviceCodeList = list.stream().map(MeterDeviceConfig::getDeviceCode).collect(Collectors.toList());
            List<RoomExpensesBill> roomExpensesBillList = baseMapper.getNewestRoomExpensesBillRecordByDeviceCode(deviceCodeList);
            if(ObjectUtils.isEmpty(roomExpensesBillList)){
                return null;
            }
            Map<String, List<RoomExpensesBill>> collect = roomExpensesBillList.stream().collect(Collectors.groupingBy(RoomExpensesBill::getDeviceCode));
            return collect;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    public Map<String, List<RoomExpensesBill>> getNewestRoomExpensesBillRecordByDeviceCode(String deviceCode){
        List<MeterDeviceConfig> list = new ArrayList<>();
        MeterDeviceConfig meterDeviceConfig = new MeterDeviceConfig();
        meterDeviceConfig.setDeviceCode(deviceCode);
        list.add(meterDeviceConfig);
        return getNewestRoomExpensesBillRecordByDeviceCode(list);
    }

    /**
     * 根据时间段获取ES能耗数据
     * @param settlementTimePeriod  查询时间段
     * @param list MeterDeviceConfig对象List
     * @return
     */
    @Override
    public List<EnergyConsumption> getEnergyConsumptionDateList(Date[] settlementTimePeriod, List<MeterDeviceConfig> list){
        List<EnergyConsumption> energyConsumptionDateList = null;
        try {
            EnergyConsumption energyConsumption = new EnergyConsumption();
            energyConsumption.setQueryPayTimeStart(settlementTimePeriod[0]);
            energyConsumption.setQueryPayTimeEnd(settlementTimePeriod[1]);
            Page page = new Page(1,0);
            energyConsumptionDateList = energyConsumptionService.getEnergyConsumptionDate(page, energyConsumption, list);
        } catch (Exception e) {
            log.error("ES查询异常：" + e.getMessage());
            throw e;
        }
        return energyConsumptionDateList;
    }

    @Override
    public List<EnergyConsumption> getEnergyConsumptionDateListNew(Date[] settlementTimePeriod, List<MeterDeviceConfig> list){
        List<EnergyConsumption> energyConsumptionDateList = null;
        try {
            EnergyConsumption energyConsumption = new EnergyConsumption();
            energyConsumption.setQueryPayTimeStart(settlementTimePeriod[0]);
            energyConsumption.setQueryPayTimeEnd(settlementTimePeriod[1]);
            Page page = new Page(1,0);
            energyConsumptionDateList = energyConsumptionService.getEnergyConsumptionDateSupplement(page, energyConsumption, list);
        } catch (Exception e) {
            log.error("ES查询异常：" + e.getMessage());
            throw e;
        }
        return energyConsumptionDateList;
    }

    /**
     * 批量处理数据，不再区分正常数据和异常数据
     * @param energyConsumptionDateNormal
     */
    public void handlerData(List<EnergyConsumption> energyConsumptionDateNormal){
        for (EnergyConsumption ec:
                energyConsumptionDateNormal) {
            calculation(ec, null, 0);
        }
    }

    /**
     * 读表数据扣费方法(单个处理数据)
     * @param ec
     */
    @Override
    public void calculation(EnergyConsumption ec, String operator, Integer remedyStatus){
        Integer billingId = baseMeterBillingMapper.getBillingIdByRoomAndMeterType(ec.getRoomId(), ec.getMeterType());
        if(billingId == null ){
            return ;
        }
        BaseBillingHistory baseBilling = billingStandardService.getNewBaseBillingInfoById(billingId);
        GlobalRoomInfo globalRoomInfo = roomManagerService.getGlobalRoomInfo(ec.getRoomId());

        //房间余额扣除
        BigDecimal expensesMoney = baseBilling.getPrice().multiply(ec.getAmount());
        if(expensesMoney.compareTo(new BigDecimal(0.0)) == 0){
            log.info("消费为0则不计入计算扣费");
            return ;
        }
        if(ec.getMeterType().equals(0)){
            deductionBalance(ec.getRoomId(), expensesMoney, null);
        }else if(ec.getMeterType().equals(1)){
            deductionBalance(ec.getRoomId(), null, expensesMoney);
        }else {
            return ;
        }

        //保存消费记录
        RoomExpensesBill roomExpensesBill = new RoomExpensesBill()
                .setContractId(globalRoomInfo.getRoomTenantryInfo().getId())
                .setSettlementTimeStart(ec.getQueryPayTimeStart())
                .setSettlementTimeEnd(ec.getQueryPayTimeEnd())
                .setEnergyAmount(ec.getAmount())
                .setDeviceCode(ec.getDeviceCode())
                .setConsumeFee(expensesMoney)
                .setDeductionStatus(0)
                .setBillingId(baseBilling.getBillingId())
                .setRemedyStatus(remedyStatus)
                .setOperator(operator)
                .setEndReadAmount(ec.getEndRead())
                .setVersion(baseBilling.getVersion());
        saveExpensesBill(roomExpensesBill);
    }

    /**
     * 扣费
     * @param roomId
     * @param waterBalance
     * @param electricityBalance
     * @return
     */
    @Override
    public Object[] deductionBalance(Integer roomId, BigDecimal waterBalance, BigDecimal electricityBalance) {
        Object[] result = new Object[2];
        RoomInfo roomInfo = roomInfoMapper.selectById(roomId);
        if(waterBalance != null){
            roomInfo.setWaterBalance(roomInfo.getWaterBalance().subtract(waterBalance));
        }

        if(electricityBalance != null){
            roomInfo.setElectricityBalance(roomInfo.getElectricityBalance().subtract(electricityBalance));
        }
        roomInfoMapper.updateById(roomInfo);
        meterServiceControl.executeDeviceCommand(roomInfo);
        result[0] = true;
        result[1] = "success";
        return result;
    }

    /**
     * 保存消费记录
     * @param roomExpensesBill
     */
    @Override
    public void saveExpensesBill(RoomExpensesBill roomExpensesBill) {
        roomExpensesBill.setCreateTime(new Date());
        roomExpensesBill.setDeductionStatus(0);
        baseMapper.insert(roomExpensesBill);
    }

    /**
     * 获取补录设备数据列表s
     * @param deviceCode
     * @param codeGroup 是否按照设备分组
     * @return
     */
    public List<MeterRemedy> getRemedyDeviceList(String deviceCode, Boolean codeGroup){
        List<MeterRemedy> meterRemedyList = null;
        if(codeGroup){
            meterRemedyList = meterRemedyMapper.getNewRemedyDataGroup(deviceCode);
        }else {
            meterRemedyList = meterRemedyMapper.getNewRemedyData(deviceCode);
        }
        return meterRemedyList;
    }

    /**
     * 根据定时任务运行状态获取上次定时任务查询时间段
     * @return
     */
    @Override
    public Date[] getLastSettlementTimePeriod(){
        Date[] settlementDate = new Date[2];
        QueryWrapper qw = new QueryWrapper();
        qw.orderByDesc("id");
        List<ExpensesTimerTask> list = expensesTimerTaskMapper.selectList(qw);
        if(list == null || list.isEmpty()){
            settlementDate[0] = null;
            settlementDate[1] = null;
            return settlementDate;
        }
        ExpensesTimerTask expensesTimerTask = list.get(0);
        settlementDate[0] = expensesTimerTask.getSettlementTimeStart();
        settlementDate[1] = expensesTimerTask.getSettlementTimeEnd();
        return settlementDate;
    }

    /**
     * 根据定时任务运行状态获取本次定时任务查询时间段
     * @return
     */
    @Override
    public Date[] getCurrentSettlementTimePeriod() {
        Date[] settlementDate = new Date[2];
        QueryWrapper qw = new QueryWrapper();
        qw.orderByDesc("id");
        Page page = new Page();
        page.setCurrent(1);
        page.setSize(1);
        IPage<ExpensesTimerTask> expensesTimerTaskPage = expensesTimerTaskMapper.selectPage(page, qw);
        if(expensesTimerTaskPage == null){
            settlementDate[0] = null;
            settlementDate[1] = new Date();
            return settlementDate;
        }
        List<ExpensesTimerTask> list = expensesTimerTaskPage.getRecords();

        if(ObjectUtils.isEmpty(list)){
            settlementDate[0] = null;
            settlementDate[1] = new Date();
            return settlementDate;
        }
        ExpensesTimerTask expensesTimerTask = list.get(0);
        settlementDate[0] = expensesTimerTask.getSettlementTimeEnd();
        settlementDate[1] = new Date();
        return settlementDate;
    }


    /**
     * 保存定时任务运行时间段
     * @param expensesTimerTask
     * @param settlementDates
     */
    private void saveSettlementTimePeriod(ExpensesTimerTask expensesTimerTask, Date[] settlementDates){
        expensesTimerTask.setSettlementTimeStart(settlementDates[0]);
        expensesTimerTask.setSettlementTimeEnd(settlementDates[1]);
        expensesTimerTaskMapper.insert(expensesTimerTask);
    }

}
